Skip to content

Extract PointsPath into AwardProvider interface (work-z6zi)#8

Merged
ak2k merged 4 commits into
mainfrom
worktree-z6zi-award-provider
May 15, 2026
Merged

Extract PointsPath into AwardProvider interface (work-z6zi)#8
ak2k merged 4 commits into
mainfrom
worktree-z6zi-award-provider

Conversation

@ak2k

@ak2k ak2k commented May 15, 2026

Copy link
Copy Markdown
Owner

Summary

Foundation for seats.aero (work-2eoa) and any future award source. The matcher is now provider-blind: match.py:join(search, awards) takes a flat list[AwardFlight] from any provider via the new registry. PointsPath is just the first one.

Shipped as four commits — review them sequentially for the cleanest mental load:

  1. Introduce AwardProvider Protocol + provider-neutral types — adds providers/base.py. Zero behavior change.
  2. Add PointsPathProvider adapter (no caller change yet) — wraps the existing PPClient into the new Protocol. Conversion from PP's OutboundFlight + PerCabinMilesPricing into the neutral AwardFlight/CabinAward types lives in the adapter.
  3. Refactor match.py + wire CLI through registryjoin() signature becomes (search, list[AwardFlight]). pp/cli.py:run_pp_for_search now drives the registry fan-out instead of instantiating PPClient directly. Renderer reads provider/program/funding_banks directly off AwardFlight; PP-only table gets a source column.
  4. Tests — registry stub-provider tests pin the fan-out + per-provider-isolation contract.

What's deferred

  • File moves (pp/{auth,client,models,match}.pyproviders/pointspath/): pure rename + deprecation shim. Mechanical follow-up — no capability change.
  • Render-replace (cash table → augmented table single transition): UX concern, independently shippable.
  • flight auth list + per-provider auth subcommands: scoped to when ≥2 providers exist.
  • seats.aero: work-2eoa, now unblocked by this PR.

Notable deletions

  • use_inbound parameter on match.join() — no production caller ever set is_round_trip_return=True, so inboundFlights was never populated. Reintroducible at the provider layer if a round-trip-inbound case ever lands.

Verification

  • make check green: 120 tests passing (was 115: +6 registry tests, +/- on match tests).
  • New tests: tests/test_registry.py.
  • Refactored: tests/pp/test_match.py (now constructs AwardFlight directly instead of AirlineSearchResponse).

Test plan

  • flight search EWR MIA --dep 2026-08-15 → gflight + PP overlay still renders.
  • flight search JFK LHR --dep 2026-08-15 --backend matrix → matrix + PP overlay still renders.
  • flight search EWR MIA --dep 2026-08-15 --pp-only → PP-only table renders with the new source column.
  • flight search EWR MIA --dep 2026-08-15 --no-pp → cash only, no PP.

Closes work-z6zi.

ak2k added 4 commits May 15, 2026 12:33
Lays the groundwork for plugging in seats.aero (work-2eoa) as a second
award source. Today PP is the only provider; the Protocol + AwardFlight
shape are designed around the identity fields the matcher already keys on
(flight#+date and route+time) plus the per-provider metadata the renderer
already consumes (program, banks, miles_to_cash_ratio).

No behavior change in this commit — base.py is unused until the
PointsPath adapter lands in the next commit.
Wraps the existing PPClient + pricing-info into the AwardProvider
Protocol. Conversion from PP's OutboundFlight + PerCabinMilesPricing into
the neutral AwardFlight/CabinAward types lives here so future providers
(seats.aero) can build the same neutral output from their own raw shapes.

Cabin-award translation logic is duplicated from pp.match._cabin_awards
to keep the adapter self-contained; the duplicate goes away in the next
commit when match.py is refactored to consume AwardFlight directly.
…egistry

The matcher's signature becomes provider-blind: `join(search, awards)` where
`awards` is a flat `list[AwardFlight]` from any number of providers. The
existing (flight#, date) and (route, time) keys stay — they were already
provider-agnostic; only the input shape changed.

`pp/cli.py:run_pp_for_search` now drives the providers/registry fan-out
rather than instantiating PPClient directly. Today the registry yields
PointsPath only; seats.aero (work-2eoa) plugs in by adding one entry and
its own `provider.py`/`is_configured()` pair — no caller-side change.

Drops the unused `use_inbound` parameter from join(): no production caller
ever set is_round_trip_return=True, so `inboundFlights` was never populated.
The future round-trip-inbound case can be re-added at the provider layer
(distinct `search_leg_inbound` method or similar) if it ever lands.

Renderer reads provider/program/funding_banks directly off AwardFlight;
the PP-only table picks up a `source` column so seats.aero's rows are
distinguishable when it joins.
Stub-provider tests pin the contract: per-leg concatenation across enabled
providers; per-provider exceptions are isolated (one failure doesn't sink
the others); empty provider list returns empty list.
@ak2k ak2k merged commit 47aa0d2 into main May 15, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant